package com.atguigu.gmall.gateway.filter;

import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.IpUtil;
import com.atguigu.gmall.common.util.Json;
import com.atguigu.gmall.gateway.properties.AuthUrlProperties;
import lombok.extern.slf4j.Slf4j;

import com.atguigu.gmall.user.entities.UserInfo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @ClassName UserAuthGlobalFilter
 * @Description TODO
 * @Author king
 * @Date 2022/11/15 18:16
 * @Version 1.0
 */
@Slf4j
@Component
public class UserAuthGlobalFilter implements GlobalFilter {


    @Autowired
    AuthUrlProperties urlProperties;

    @Autowired
    StringRedisTemplate redisTemplate;

    AntPathMatcher matcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取当前请求的路径
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        log.info("请求开始:" + path);
        //判断
        List<String> authUrl = urlProperties.getNoAuthUrl();
        long count = authUrl
                .stream()
                .filter(pattern-> matcher.match(pattern, path))
                .count();

        if (count>0){
            //直接放行
            return chain.filter(exchange);
        }

        //判断=======内部接口
        List<String> denyUrl = urlProperties.getDenyUrl();
        long denyCount = denyUrl.stream().filter(pattern -> matcher.match(pattern, path)).count();
        if (denyCount>0){
            //给浏览器写一个JSON出去
            Result<String> result = Result.build("", ResultCodeEnum.FAIL);
            return responseJson(result,exchange);
        }

        // ========用户id透传========
        long authCount = urlProperties.getAuthUrl()
                .stream()
                .filter(pattern -> matcher.match(pattern, path))
                .count();
        if (authCount>0){
            //验证登录,没登录直接打回
            String token = getUserHeaderInfo(exchange,"token");

            boolean b = existLoginUser(token);
            //2,判断是否有token
            if (StringUtils.isEmpty(token)){
                //3)滚回登录页,让你登录
                return RedirectPage(exchange,urlProperties.getLoginPage());
            }
        }
        //就是正常请求: 登录成功,普通请求
        //TODO 用户id透传; 如果有用户id就进行透传
        String userToken = getUserHeaderInfo(exchange, "token");
        UserInfo userInfo = getLoginUserInfo(userToken);

        //普通请求: 没带不用验证,带了就一定得对=======登录请求:  由于带了,就必须进行合法性校验
        //带个假令牌来骗我
        if (!StringUtils.isEmpty(userToken)) {
            //如果是假令牌就打回去
            boolean flag = existLoginUser(userToken);
            if (!flag){ //有令牌,但是是假令牌,所有redis返回false
                return RedirectPage(exchange,urlProperties.getLoginPage());
            }
            //存在,但是不一致
            if (flag){
                String ip = userInfo.getIp();
                String address = IpUtil.getGatwayIpAddress(exchange.getRequest());
                if (!address.equals(ip)){
                    //令牌存在盗用风险
                    return RedirectPage(exchange,urlProperties.getLoginPage());
                }
            }
        }
        //测试一波

        //下面是真令牌
        //去redis中拿到真正的userInfo信息


        //======是否一定有userId?
        //网关:
        //1),谁来都行
        //2),谁来都不行
        //3),自定义规则; 各种规则; 需要登录的要验证登录
        //4),剩下的都是普通请求; 直接放


        //放行 用户ID有那个放哪个
        return userIdThroughtChain(exchange,chain,userInfo);
    }

    private Mono<Void> userIdThroughtChain(ServerWebExchange exchange, GatewayFilterChain chain, UserInfo userInfo) {

        //临时Id,只要前端带了一定透彻
        String tempId = getUserHeaderInfo(exchange, "userTempId");

//        if (userInfo == null){
//            return chain.filter(exchange);
//        }
        ServerHttpRequest.Builder builder = exchange.getRequest()
                .mutate();

        if (!StringUtils.isEmpty(tempId)){
            builder.header(RedisConst.U_TEMP_ID_HEADER, tempId);
        }
        if (userInfo!=null){
            builder.header(RedisConst.UID_HEADER, userInfo.getId().toString());
        }

        ServerWebExchange newExchange = exchange
                .mutate()
                .request(builder.build())
                .response(exchange.getResponse())
                .build();

        //改掉请求头中的信息
//        ServerHttpRequest request = exchange.getRequest();
//        request.getHeaders().add(RedisConst.UID_HEADER, userInfo.getId().toString());


        return chain.filter(newExchange);
    }

    private UserInfo getLoginUserInfo(String token) {
        if (StringUtils.isEmpty(token)) {
            return null;
        }
        String key = RedisConst.LOGIN_USER + token;

        //假令牌====用户信息过期

        String userJson = redisTemplate.opsForValue().get(key);
        if (StringUtils.isEmpty(userJson)) {
            return null;
        }
        UserInfo info = Json.Obj(userJson, UserInfo.class);
        return info;

    }

    private boolean existLoginUser(String token) {
        String key = RedisConst.LOGIN_USER + token;
        Boolean hasKey = redisTemplate.hasKey(key);

        return hasKey;

    }

    /**
     * 重定向到指定页面
     * @param exchange
     * @param loginPage
     * @return
     */
    private Mono<Void> RedirectPage(ServerWebExchange exchange, String loginPage) {
        ServerHttpResponse response = exchange.getResponse();
        String path = null;
        try {
            path = exchange.getRequest().getURI().toURL().toString();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        //1,重定向: HTTP; 302状态码 + Location响应头
        response.setStatusCode(HttpStatus.FOUND);

        String url = loginPage + "?originUrl=" + path;
        response.getHeaders().add("Location", url);


        //2,无限重定向问题
        //命令浏览器删除假令牌
        ResponseCookie tokenCookie = ResponseCookie.from("token", "")
                .maxAge(0).path("/").domain(".gmall.com").build();
        ResponseCookie infoCookie = ResponseCookie.from("userInfo", "")
                .maxAge(0).path("/").domain(".gmall.com").build();

        response.addCookie(tokenCookie);
        response.addCookie(infoCookie);

        return response.setComplete();

    }

    /**
     * 获取用户令牌
     * @param exchange
     * @return
     */
    private String getUserHeaderInfo(ServerWebExchange exchange,String key) {

        ServerHttpRequest request = exchange.getRequest();
        //1,先看看cookie中有没有token
        MultiValueMap<String, HttpCookie> cookies = request.getCookies();
        if (cookies != null){
            HttpCookie token = cookies.getFirst(key);
            if (token != null){
                String value = token.getValue();
                if (!StringUtils.isEmpty(token)){
                    return value;
                }
            }

        }

        //2,cookie没有, 再看看token头中有没有
        String token = request.getHeaders().getFirst(key);
        return token;

    }

    //响应一个json
    private Mono<Void> responseJson(Result<String> result,ServerWebExchange exchange) {

        ServerHttpResponse response = exchange.getResponse();
        //DataBuffer
        String jsonStr = Json.toJsonStr(result);
        DataBuffer dataBuffer = response.bufferFactory().wrap(jsonStr.getBytes(StandardCharsets.UTF_8));

        //发布者
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
        //响应结束
        return response.writeWith(Mono.just(dataBuffer));
    }
}
